module lpuart_tx(
  input            uart_clk,
  input            uart_rst_n,
  
  input            baud_clk,
  
  input            stop_1bit,
  input            stop_2bit,
  input            frame_7bit,
  input            frame_8bit,
  input            frame_9bit,
  input            pce,
  input            ps,
  input            msbfirst,
  input            fifo_en,
  
  input            tx_start,
  input            tx_en,
  input    [8:0]   tx_data_reg,
  input            txfrq,
  input            sbkrq,
  input            cts_en,
  input            cts_n,
  
  output           tx_pop,
  input    [8:0]   tx_pop_data,
  input            tx_rempty,

  output           tx_idle,
  output           tx_finish,
  output reg       tx_out_en,
  output reg       tx_out
);

localparam ST_IDLE          =  5'h0;
localparam ST_TX_START      =  5'h1;
localparam ST_TX_DATA0      =  5'h2;
localparam ST_TX_DATA1      =  5'h3;
localparam ST_TX_DATA2      =  5'h4;
localparam ST_TX_DATA3      =  5'h5;
localparam ST_TX_DATA4      =  5'h6;
localparam ST_TX_DATA5      =  5'h7;
localparam ST_TX_DATA6      =  5'h8;
localparam ST_TX_DATA7      =  5'h9;
localparam ST_TX_DATA8      =  5'ha;
localparam ST_TX_PARITY     =  5'hb;
localparam ST_TX_STOP1      =  5'hc;
localparam ST_TX_STOP2      =  5'hd;
localparam ST_TX_BRK        =  5'he;
localparam ST_TX_IDLE       =  5'hf;
localparam ST_BRK_END       =  5'h10;

reg  [3:0] tx_cur_st;
reg  [3:0] tx_nxt_st;
wire [7:0] frame_content_baud_num;
wire [7:0] break_frame_baud_num;

assign frame_content_baud_num  = frame_7bit ? 8'd8 :  // start_bit + data_bit = (1 + 7) = 8
                                 frame_8bit ? 8'd9 :
                                 frame_9bit ? 8'd10 : 8'd9;

assign break_frame_baud_num  = frame_content_baud_num + 2;

assign clr_sbkrq = (tx_cur_st == ST_BRK_END) && (tx_nxt_st == ST_IDLE);

reg dly_sbkrq;
always@(posedge uart_clk, negedge uart_rst_n)
  if(!uart_rst_n)
    dly_sbkrq <= 1'b0;
  else if(clr_sbkrq)
    dly_sbkrq <= 1'b0;
  else if(tx_cur_st == ST_TX_BRK)
    dly_sbkrq <= sbkrq;

reg dly_tx_start;
always@(posedge uart_clk, negedge uart_rst_n)
  if(!uart_rst_n)
    dly_tx_start <= 1'b0;
  else
    dly_tx_start <= tx_start;

reg dly_tx_en;
always@(posedge uart_clk, negedge uart_rst_n)
  if(!uart_rst_n)
    dly_tx_en <= 1'b0;
  else
    dly_tx_en <= tx_en;

reg [7:0] frame_baud_cnt;
always@(posedge uart_clk, negedge uart_rst_n) begin
  if(!uart_rst_n)
    frame_baud_cnt <= 8'h0;
  else if(baud_clk && (tx_cur_st == ST_IDLE))
    frame_baud_cnt <= 8'h0;
  else if(baud_clk)
    frame_baud_cnt <= frame_baud_cnt + 1'b1;
end

always@(posedge uart_clk, negedge uart_rst_n)begin:TX_FSM_SEQ_PROC
  if(!uart_rst_n)
    tx_cur_st <= 4'h0;
  else if(~tx_en)
    tx_cur_st <= 4'h0;
  else
    tx_cur_st <= tx_nxt_st;
end

always@(*) begin:TX_FSM_PROC
  tx_nxt_st = tx_cur_st;
  case(tx_cur_st)

    ST_IDLE: begin
      if(sbkrq && ~dly_sbkrq)
        tx_nxt_st = ST_TX_BRK;
      else if(tx_start ^ dly_tx_start)
        tx_nxt_st = ST_TX_START;
      else if(tx_en && ~dly_tx_en)
        tx_nxt_st = ST_TX_IDLE;
    end

    ST_TX_START: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_DATA0;
    end
    ST_TX_DATA0: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_DATA1;
    end
    ST_TX_DATA1: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_DATA2;
    end
    ST_TX_DATA2: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_DATA3;
    end
    ST_TX_DATA3: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_DATA4;
    end
    ST_TX_DATA4: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_DATA5;
    end
    ST_TX_DATA5: begin
      if(baud_clk)
        if(pce && frame_7bit)
          tx_nxt_st =  ST_TX_PARITY;
        else
          tx_nxt_st = ST_TX_DATA6;
    end
    ST_TX_DATA6: begin
      if(baud_clk)
        if(frame_7bit)
          tx_nxt_st = ST_TX_STOP1;
        else if(pce && frame_8bit)
          tx_nxt_st = ST_TX_PARITY;
        else
          tx_nxt_st = ST_TX_DATA7;
    end
    ST_TX_DATA7: begin
      if(baud_clk)
        if(frame_8bit)
          tx_nxt_st = ST_TX_STOP1;
        else if(pce && frame_9bit)
          tx_nxt_st = ST_TX_PARITY;
        else
          tx_nxt_st = ST_TX_DATA8;
    end
    ST_TX_DATA8: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_STOP1;
    end
    ST_TX_PARITY: begin
      if(baud_clk)
        tx_nxt_st = ST_TX_STOP1;
    end
    ST_TX_STOP1: begin
      if(baud_clk)
        if(stop_2bit)
          tx_nxt_st = ST_TX_STOP2;
        else
          tx_nxt_st = ST_IDLE;
    end
    ST_TX_STOP2: begin
      if(baud_clk)
        tx_nxt_st =  ST_IDLE;
    end
    ST_TX_BRK: begin
      if(baud_clk && (frame_baud_cnt == break_frame_baud_num))
        tx_nxt_st =  ST_BRK_END;
    end
    ST_TX_IDLE: begin
      if(baud_clk && (frame_baud_cnt == frame_content_baud_num))
        tx_nxt_st = ST_TX_STOP1;
    end
    ST_BRK_END: begin
      if(baud_clk && (frame_baud_cnt == 8'h1f))
        tx_nxt_st =  ST_TX_STOP1;
    end
    default: tx_nxt_st = ST_IDLE;
  endcase
end


assign driver_low = (tx_cur_st == ST_TX_START) || (tx_cur_st == ST_TX_BRK);

assign driver_high = (tx_cur_st == ST_IDLE) || (tx_cur_st == ST_TX_IDLE) ||
                     (tx_cur_st == ST_TX_STOP1) || (tx_cur_st == ST_TX_STOP2) ||
                     (tx_cur_st == ST_BRK_END);

assign driver_parity = (tx_cur_st == ST_TX_PARITY);

assign shift_en = ((tx_cur_st == ST_TX_DATA0) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA1) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA2) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA3) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA4) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA5) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA6) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA7) && baud_clk) ||
                  ((tx_cur_st == ST_TX_DATA8) && baud_clk);

wire   tx_busy = (tx_cur_st != ST_IDLE);
assign tx_idle = (tx_cur_st == ST_IDLE);

wire [8:0] tx_data      = fifo_en ? tx_pop_data : tx_data_reg;
wire       load_tx_data = fifo_en ? tx_pop : dly_tx_start;
   
reg [8:0] tx_shift_reg;
always@(posedge uart_clk, negedge uart_rst_n)begin:TX_SHIFT_REG_PROC
  if(!uart_rst_n)begin
    tx_shift_reg <= 9'h0;
  end 
  else if(load_tx_data) begin
    tx_shift_reg <= tx_data;
  end
  else if(shift_en) begin
    tx_shift_reg <= {1'b0, tx_shift_reg[8:1]};
  end
end

reg  even_parity;
wire odd_parity;
always@(*)begin:PARITY_GEN_PROC
  if(pce) begin
    case({frame_9bit, frame_8bit, frame_7bit})
      3'b001:begin
        even_parity = ^(tx_data[5:0]);
      end
      3'b010:begin
        even_parity = ^(tx_data[6:0]);
      end
      3'b100:begin
        even_parity = ^(tx_data[7:0]);
      end
    endcase
  end
  else
    even_parity = 1'b0;
end

assign odd_parity = pce ? ~even_parity : 1'b0;
wire   parity_gen = ps ? odd_parity : even_parity;

reg tx_out_tmp;
always@(*)begin
  if(driver_low)
    tx_out_tmp = 1'b0;
  else if(driver_high)
    tx_out_tmp = 1'b1;
  else if(driver_parity)
    tx_out_tmp = parity_gen;
  else
    tx_out_tmp = tx_shift_reg[0];
end

always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)
    tx_out <= 1'b1;
  else
    tx_out <= tx_out_tmp;
end

always@(posedge uart_clk, negedge uart_rst_n)begin
  if(!uart_rst_n)
    tx_out_en <= 1'b0;
  else
    tx_out_en <= tx_busy;
end

assign tx_finish = (tx_cur_st == ST_TX_STOP1) && stop_1bit && baud_clk ||
                   (tx_cur_st == ST_TX_STOP2) && baud_clk;

wire tx_pop_tmp = fifo_en & tx_finish & (~tx_rempty);
assign tx_pop = cts_en ? tx_pop_tmp & (~cts_n) : tx_pop_tmp;

endmodule
